(function(global){
	var getNextColor = function(){
		var interval = Math.floor(Math.random() * 20) + 1;
		var r = Raphael.getColor();
		var i = 0;
		for(; i < interval; i++){
			r = Raphael.getColor();
		}
		return r;
	};

	// default configuration
	var conf = {
		// rectangle element position parameters
		initPosition: {
			x: 0,
			y: 0,
			w: 120,
			h: 60, 
			r: 8
		}, 

		// create a child rectangle element with this text
		initText: {
			label: 'NEW'
		}, 

		// text element locate relative to rectangle element
		textWidthFix: 40, 
		textHeightFix: 20, 

		textFontSize: 13, 
		textColor: '#fff', 
		focusTextColor: '#336633', 

		fillColor: '#ccc',
		focusFillColor: '#ccffff',
		fillColorBegin: '#000099',
		fillColorEnd: '#ff9900',

		connectLineColor: '#333', 

		rootId: 'root', 
		rootText: 'root',

		// save json in localstorage with this key by default
		serializeKey: '_main_drawer'
	};

	conf.changeColors = function(){
		var that = this;
		_.each('textColor,focusTextColor,fillColor,fillColorBegin,fillColorEnd,focusFillColor'.split(','), 
			function(it){
				that[it] = getNextColor();
				console.log(it + ' - ' + that[it]);
		});
	};

	var mockRootEl = {
		attr: function(){
			return 0;
		},

		getBBox: function(){
			return {
				x: 0,
				y: 0,

				width: 0, 
				height: 0 
			}
		}
	};

	// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
	// help methods
	var extend = function(src, dest){
		return _.extend(_.clone(src), dest);
	};

	var indexOf = function(list, field, val){
		var i = 0, len = list.length, one;
		for(; i < len; i++){
			one = list[i];
			if(val === one[field])
				return i;
		}
		return -1;
	};

	// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
	// events for raphael element
	var eve = {};
	eve.move = function(dx, dy){
		var el = this.el;
		var attrMove = {x: el.ox + dx, y: el.oy + dy};
		el.attr(attrMove);

		if(this.elText){
			var attrMoveText = {x: attrMove.x + conf.textWidthFix, 
				y: attrMove.y + conf.textHeightFix};
			this.elText.attr(attrMoveText);
		}

		this.drawer.reconnect(this);
		this.paper.safari();
	};
	eve.drag = function(e){
		var el = this.el;
		el.ox = el.attr('x')
		el.oy = el.attr('y')
		el.animate({'fill-opacity': .2}, 200);

		this.focus();

		this.drawer.unoverlay(this, e);
	};
	eve.dragend = function(e){
		var el = this.el;
		el.animate({'fill-opacity': 1}, 200);

		this.drawer.overlay(this, e);
	};
	eve.dblclick = function(){
		// you can do a lot of things if you want
		$.dialog.alert(this.toString());
	};
	eve.mouseover = function(e){
		this.drawer.overlay(this, e);
	};
	eve.mouseout = function(e){
		this.drawer.unoverlay(this, e);
	};
	eve.mouseoverText = function(e){
		this.drawer.operator.showDetail(this, e);
	};
	eve.mouseoutText = function(e){
		this.drawer.operator.hideDetail(this, e);
	};

	// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
	// element wrapper, rectangle with text
	var Linker = function(drawer, id, text, parentId, opts){
		this.drawer = drawer;
		this.paper = drawer.paper;
		this.id = id;
		this.text = text;
		this.parentId = parentId;
		this.opts = opts || {};

		this.isFocus = false;
	};
	var fn = Linker.prototype;

	fn.serialize = function(){
		var r = {};
		r.id = this.id;
		r.text = this.text;
		r.parentId = this.parentId;
		r.opts = this.opts || {};

		// use latest position
		var bbox = this.el.getBBox();
		r.opts.x = bbox.x;
		r.opts.y = bbox.y;

		return r;
	};

	fn.unserialize = function(one){
		this.text = one.text;
		this.parentId = one.parentId;
		this.opts = one.opts || {};

		this.redraw();
	};

	fn.toString = function(){
		return JSON.stringify(this.serialize());
	};

	fn.isRoot = function(){
		return conf.rootId === this.id;
	};

	fn.getRefer = function(){
		var ll = [];

		var i = 0, len = this.drawer.connections.length, one;
		for(; i < len; i++){
			one = this.drawer.connections[i];
			if(one.from == this.el || one.to == this.el){
				var r = {};
				r.line = one.line;
				r.linker = this.drawer.getOneByEl(one.to);
				r.type = one.from == this.el ? 'to' : 'from';
				ll.push(r);
				continue;
			}
		}

		return ll;
	};

	fn.init = function(){
		if(!this.isRoot()){
			this.draw();
			this.drawer.resetIndex();
		}else{
			// root has no raphael element, use a mock for position calculating
			this.el = mockRootEl;
		}
	};

	fn.redraw = function(){
		if(this.elText){
			var oldText = this.elText.attr('text');
			if(oldText !== this.text){
				this.elText.attr('text', this.text);
			}
		}

		this.setPosition(this.opts.x, this.opts.y);
	};

	fn.getFillColor = function(){
		if(this.isFirst())
			return conf.fillColorBegin;

		if(this.isLast())
			return conf.fillColorEnd;

		return conf.fillColor;
	};

	// create raphael elements
	fn.draw = function(){
		this.el = this.paper.rect(this.opts.x, this.opts.y, this.opts.w, this.opts.h, this.opts.r)
			.drag(_.bind(eve.move, this), _.bind(eve.drag, this), _.bind(eve.dragend, this))
			.dblclick(_.bind(eve.dblclick, this))
			.mouseover(_.throttle(_.bind(eve.mouseover, this), 500))
			.mouseout(_.bind(eve.mouseout, this));

		this.resetColor();

		if(this.text){
			var x = this.opts.x + conf.textWidthFix;
			var y = this.opts.y + conf.textHeightFix;
			this.elText = this.paper.text(x, y, this.text)
				.attr({'font-size': conf.textFontSize, fill: conf.textColor})
				.mouseover(_.throttle(_.bind(eve.mouseoverText, this), 500))
				.mouseout(_.bind(eve.mouseoutText, this));
		}
	};

	fn.getPosition = function(){
		var el = this.el;
		return {x: el.attr('x'), y: el.attr('y')};
	};

	fn.setPosition = function(x, y){
		this.el.attr({x: x, y: y});

		if(this.elText){
			this.elText.attr({x: x + conf.textWidthFix, 
				y: y + conf.textHeightFix});
		}

		// change line path attributes
		this.drawer.reconnect(this);
	};

	// donot use this method directly, use Drawer.remove instead
	fn.remove = function(){
		this.el.remove();
		if(this.elText)
			this.elText.remove();
	};

	fn.isFirst = function(){
		return this.drawer.getIndex(this) == 1;
	};

	// isEnd or position is the last
	fn.isLast = function(){
		if(this.opts.isEnd)
			return true;

		if(this.drawer.hasEnd()){
			return false;
		}

		return this.drawer.getIndex(this) == this.drawer.size() - 1;
	};

	fn.resetColor = function(){
		this.el.attr({fill: this.getFillColor()});
	};

	fn.focus = function(){
		if(this.isFocus)
			return;

		if(this.elText){
			this.elText.attr({'font-size': conf.textFontSize + 1, 'font-weight': 'bold'});
//			this.elText.attr({'font-size': conf.textFontSize + 1, 'font-weight': 'bold', 
//				fill: conf.focusTextColor});
		}

		// unfocus others
		this.drawer.focus(this);
		this.isFocus = true;

		var ll = this.getRefer();
		this.drawer.connectionsFocus(ll);
	};

	fn.unfocus = function(){
		if(!this.isFocus)
			return;
	
		this.resetColor();
		if(this.elText){
			this.elText.attr({'font-size': conf.textFontSize, 'font-weight': 'normal'});
//			this.elText.attr({'font-size': conf.textFontSize, 'font-weight': 'normal', 
//				fill: conf.textColor});
		}

		this.isFocus = false;
	};

	// call drawer methods
	fn.getParent = function(){
		return this.drawer.get(this.parentId);
	};
	fn.getChildren = function(){
		return this.drawer.getChildren(this.id);
	};
	fn.changeParent = function(parentId){
		// change as top
		if(conf.rootId === parentId){
			this.drawer.disconnect(this);
			this.parentId = parentId;
			return;
		}

		var fromLinker = this.drawer.get(parentId);
		if(!fromLinker)
			return;

		this.parentId = parentId;
		this.drawer.changeConnect(this, fromLinker);
	};

	// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
	var Drawer = function(paper, layoutOpts){
		this.paper = paper;

		var root = new Linker(this, conf.rootId, conf.rootText);
		this.list = [root];
		root.init();

		// initialized width/height
		this.initW = paper.width;
		this.initH = paper.height;

		// line path wrappers
		this.connections = [];

		this.layouter = new Layouter(paper, layoutOpts);
		this.operator = new Operator(this);

		this.reflow();
	};

	var fnDrawer = Drawer.prototype;

	fnDrawer.size = function(){
		return this.list.length;
	};

	// not root
	fnDrawer.hasLinker = function(){
		return this.size() > 1;
	};

	fnDrawer.isEmpty = function(){
		return !this.hasLinker();
	};

	fnDrawer.get = function(id){
		return _.findWhere(this.list, {id: id});
	};

	fnDrawer.getOneByEl = function(el){
		return _.find(this.list, function(it){
			return it.el == el;
		});
	};

	fnDrawer.getOneById = function(id){
		return _.find(this.list, function(it){
			return it.id == id;
		});
	};

	fnDrawer.hasEnd = function(){
		return _.any(this.list, function(it){
			return it.opts.isEnd;
		});
	};

	fnDrawer.getIndex = function(linker){
		var i = 1, len = this.list.length, one;
		for(; i < len; i++){
			var one = this.list[i];
			if(one.id == linker.id)
				return i;
		}
		return -1;
	};

	// calculate index that it in parent's children list
	fnDrawer.getSeq = function(linker){
		var countNum = 0, i = 0, len = this.list.length, one;
		for(; i < len; i++){
			one = this.list[i];
			if(one === linker)
				return countNum;
			else if(one.parentId === linker.parentId)
				countNum++;
		}
	};

	// set lastEnd
	fnDrawer.setToEnd = function(linker){
		linker.opts.isLastEnd = 1;
		var endLinker = this.getEnd();
		if(endLinker && endLinker != linker){
			this.connections.push(this.paper.connection(linker.el, 
				endLinker.el, 
				conf.connectLineColor));
		}
	};

	fnDrawer.setEnd = function(id){
		var target = this.getOneById(id);
		if(!target)
			return;

		var i = 1, len = this.list.length, one;
		for(; i < len; i++){
			one = this.list[i];
			one.opts.isEnd = 0;
		}
		target.opts.isEnd = 1;
		this.resetIndex();
	};

	fnDrawer.getEnd = function(){
		if(this.isEmpty())
			return null;

		// isEnd or last one
		var one = _.find(this.list, function(it){
			return it.opts.isEnd == 1;
		});
		if(one)
			return one;

		return this.list[this.list.length - 1];
	};

	fnDrawer.resetIndex = function(){
		if(this.isEmpty())
			return;

		var i = 1, len = this.list.length, one;
		for(; i < len; i++){
			var one = this.list[i];
			one.resetColor();
		}
	};

	fnDrawer.changeStyle = function(){
		conf.changeColors();
		this.resetIndex();
	};

	fnDrawer.clear = function(){
		this.operator.hideIfShow();

		var i = 1, len = this.list.length, one;
		for(; i < len; i++){
			one = this.list[i];
			// remove top ones only, as each will remove it's children
			if(conf.rootId === one.parentId)
				this.remove(one.id, true);
		}
	};

	fnDrawer.connectionsUnfocus = function(){
		var i = 0, len = this.connections.length, one;
		for(; i < len; i++){
			one = this.connections[i];
			one.line.attr({stroke: '#000'});
		}
	};

	fnDrawer.connectionsFocus = function(ll){
		this.connectionsUnfocus();
		_.each(ll, function(it){
			it.line.attr({stroke: 'red'});
		});
	};

	fnDrawer.serialize = function(){
		var list = [];
		var i = 1, len = this.list.length, one, opts;
		for(; i < len; i++){
			one = this.list[i];
			list.push(one.serialize());
		}

		return list;
	};

	fnDrawer.unserialize = function(list){
		this.operator.hideIfShow();

		var i = 0, len = list.length, one, linker;
		for(; i < len; i++){
			one = list[i];
			linker = this.get(one.id);
			if(linker){
				linker.unserialize(one);
			}else{
				this.create(one.id, one.text, one.parentId, one.opts);
			}
		}

		// remove those not exists in storage
		var idSaved = _.pluck(list, 'id');

		// loop reverse
		var j = this.list.length - 1, linker;
		// as index 0 is root
		for(; j > 0; j--){
			linker = this.list[j];
			if(idSaved.indexOf(linker.id) === -1)
				this.remove(linker.id);
		}
	};

	fnDrawer.setSize = function(w, h){
		this.paper.setSize(w, h);
	};

	fnDrawer.reflow = function(layoutOpts){
		if(layoutOpts)
			this.layouter.reload(layoutOpts);

		if(!this.hasLinker())
			return;

		var i = 1, len = this.list.length, one, opts;
		for(; i < len; i++){
			one = this.list[i];
			opts = extend(conf.initPosition, {});
			opts.seq = this.getSeq(one);
			opts = this.fixLayout(this.get(one.parentId), opts);
			one.setPosition(opts.x, opts.y);
		}
	};

	// resize paper to fix all elements
	fnDrawer.resize = function(){
		if(!this.hasLinker())
			return;

		var w = this.paper.width;
		var h = this.paper.height;

		var maxLayoutX = 0;
		var maxLayoutY = 0;

		var i = 1, len = this.list.length, one, bbox, x, y;
		for(; i < len; i++){
			one = this.list[i];
			bbox = one.el.getBBox();
			x = bbox.x + bbox.width;
			y = bbox.y + bbox.height;

			if(x > maxLayoutX)
				maxLayoutX = x;
			if(y > maxLayoutY)
				maxLayoutY = y;
		}

		var targetW = maxLayoutX > w ? maxLayoutX : 
			(maxLayoutX < this.initW ? this.initW : w);
		var targetH = maxLayoutY > h ? maxLayoutY : 
			(maxLayoutY < this.initH ? this.initH : h);

		this.setSize(targetW, targetH);
	};

	// calculate this element's x/y by parent and layout parameters
	fnDrawer.fixLayout = function(parentLinker, opts){
		if(!parentLinker)
			return;

		var el = parentLinker.el;
		var bbox = el.getBBox();
		if(!opts.x)
			opts.x = bbox.x + bbox.width + this.layouter.opts.linkerWidthStep;

		if(!opts.y){
			opts.y = bbox.y + bbox.height + this.layouter.opts.linkerHeightStep;
			// one may have more than one child, y++, seq begin with 0
			if(opts.seq)
				opts.y += (opts.h + this.layouter.opts.linkerHeightStep) * opts.seq;
		}

		return opts;
	};

	// create a linker
	fnDrawer.create = function(id, text, parentId, opts){
		parentId = parentId || conf.rootId;
		opts = extend(conf.initPosition, opts);

		var parentLinker = this.get(parentId);

		// index of linker created
		opts.seq = parentLinker.getChildren().length;
		this.fixLayout(parentLinker, opts);

		var linker = new Linker(this, id, text, parentId, opts);
		this.list.push(linker);
		linker.init();

		// not top need line path connect between it and it's parent
		if(conf.rootId !== parentId)
			this.connections.push(this.paper.connection(parentLinker.el, 
				linker.el, 
				conf.connectLineColor));

		if(opts.isLastEnd){
			this.setToEnd(linker);
		}

		// resize paper to fix width/height
		this.resize();

		this.afterCreate();
	};

	fnDrawer.afterCreate = function(){
		var i = 0, len = this.list.length, one;
		for(; i < len; i++){
			one = this.list[i];
			if(one.opts.fillColor){
				one.el.attr({fill: one.opts.fillColor});
			}
		}
	};

	fnDrawer.focus = function(linker){
		this.current = linker;

		var i = 0, len = this.list.length, one;
		for(; i < len; i++){
			one = this.list[i];
			if(one !== linker && !one.isRoot())
				one.unfocus();
		}
	};

	fnDrawer.getChildren = function(id){
		id = id || conf.rootId;
		return _.filter(this.list, function(it){
			return it.parentId == id;
		});
	};

	fnDrawer.reconnect = function(linker){
		var i = 0, len = this.connections.length, one, needReconnect;
		for(; i < len; i++){
			one = this.connections[i];
			needReconnect = linker ? (one.from === linker.el || one.to === linker.el) : true;
			if(needReconnect)
				this.paper.connection(one);
		}
	};

	fnDrawer.changeConnect = function(linker, fromLinker){
		var i = 0, len = this.connections.length, one;
		for(; i < len; i++){
			one = this.connections[i];
			if(one.to !== linker.el){
				continue;
			}

			// use exists one from linker
			one.from = fromLinker.el;
			this.paper.connection(one);
			return;
		}

		// create new connection
		this.connections.push(this.paper.connection(fromLinker.el, linker.el, conf.connectLineColor));
	};

	// id -> linker's id or linker object
	// withChildren = true -> remove it's children before remove itself 
	fnDrawer.remove = function(id, withChildren){
		var isIdString = typeof id === 'string';
		var linker = isIdString ? this.get(id) : id;
		if(!linker)
			return;

		id = isIdString ? id : linker.id;

		var childrenList = this.getChildren(id);
		if(childrenList.length){
			var i = 0, len = childrenList.length, one;
			for(; i < len; i++){
				one = childrenList[i];

				if(withChildren){
					this.remove(one, true);
				}else{
					one.changeParent(linker.parentId);
				}
			}
		}

		// 关联4个，已经够多了
		this.disconnect(linker, 4);
		linker.remove();

		var index = indexOf(this.list, 'id', id);
		if(index !== -1)
			this.list.splice(index, 1);

		this.operator.hideIfShow(id);
		this.resize();
		this.resetIndex();
	};

	fnDrawer.disconnect = function(linker, doTimes){
		var i = 0, len = this.connections.length, one;
		for(; i < len; i++){
			one = this.connections[i];

			var skip = one.to !== linker.el;
			// to it or from it to the end
			if(linker.opts.isLastEnd && one.from == linker.el){
				skip = false;
			}

			if(skip){
				continue;
			}

			// refer to base.js
			if(one.bg)
				one.bg.remove();
			if(one.line)
				one.line.remove();

			this.connections.splice(i, 1);
			break;
		}

		if(doTimes){
			this.disconnect(linker, doTimes - 1);
		}
	};

	fnDrawer.overlay = function(linker, e){
		var pos = this.layouter.getHandlerPosition(linker);
		if(!pos)
			return;

		pos.linkerId = linker.id;
		this.operator.showHandler(pos, e);
	};

	fnDrawer.unoverlay = function(linker, e){
		this.operator.hideHandler(e);
	};

	// only Drawer is global, important
	global.Drawer = Drawer;

	// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
	var defaultLayouterOpts = {
		linkerWidthStep: 30, 
		linkerHeightStep: 8,

		handlerWidth: 80, 
		handlerHeight: 18, 

		detailWidthFix: 10,
		detailHeightFix: 10, 

		detailDivWidth: 300,
		detailDivHeight: 80
	};

	var Layouter = function(paper, opts){
		this.paper = paper;
		this.reload(opts);
	};

	var fnLayouter = Layouter.prototype;

	fnLayouter.reload = function(opts){
		this.opts = extend(defaultLayouterOpts, opts);
	};

	fnLayouter.getHandlerPosition = function(linker){
		var el = linker.el;
		var bbox = el.getBBox();
		if(!bbox)
			return null;
		return {x: bbox.x + bbox.width - this.opts.handlerWidth, y: bbox.y};
	};

	fnLayouter.getDetailPosition = function(linker){
		var elText = linker.elText;
		var bbox = elText.getBBox();

		var opts = {x: bbox.x + bbox.width + this.opts.detailWidthFix, 
			y: bbox.y + bbox.height + this.opts.detailHeightFix};

		// show left instead right
		if(opts.x + this.opts.detailDivWidth > this.paper.width)
			opts.x -= (bbox.width + this.opts.detailWidthFix) * 2 + this.opts.detailDivWidth;

		// show top instead bottom
		if(opts.y + this.opts.detailDivHeight > this.paper.height)
			opts.y -= (bbox.height + this.opts.detailHeightFix) * 2 + this.opts.detailDivHeight;

		return opts;
	};

	// *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
	// svg cooperate with dom, need jquery
	var Operator = function(drawer){
		this.selectorHandler = '#linker-handler';
		this.selectorDetail = '#linker-detail';

		this.drawer = drawer;
		this.layouter = drawer.layouter;

		// save json
		this.storage = new MyStorage();
		this.init();
	};
	
	var fnOperator = Operator.prototype;

	fnOperator.init = function(){
		var that = this;
		$(this.selectorHandler).width(this.layouter.opts.handlerWidth)
			.height(this.layouter.opts.handlerHeight)
			.click(function(e){
				var target = $(e.target);
				// edit or close
				var action =  'plus';
				if(target.is('.icon-remove'))
					action = 'remove';
				else if(target.is('.icon-edit'))
					action = 'edit';
				else if(target.is('.icon-step-forward'))
					action = 'forward';
	
				var id = $(that.selectorHandler).attr('data-id');
				that[action](id);
			});

		// never fire
		$(this.selectorDetail)
			.mouseleave(function(e){
				if('tspan' !== e.toElement.nodeName)
					$(this).fadeOut('fast');
			});

		// example's button events
		$('#action-reflow').click(function(){
			that.drawer.reflow();
		});
		$('#action-resize').click(function(){
			that.drawer.resize();
		});
		$('#action-save').click(function(){
			that.save(conf.serializeKey);
			$.dialog.alert('Save OK');
		});
		$('#action-fetch').click(function(){
			that.fetch(conf.serializeKey);
		});
		$('#action-style').click(function(){
			that.drawer.changeStyle();
		});
		$('#action-set-end').click(function(){
			var current = that.drawer.current;
			if(current)
				that.drawer.setEnd(current.id);
		});
		$('#action-clear').click(function(){
			that.drawer.clear();
		});
	};

	fnOperator.remove = function(id){
		this.drawer.remove(id);
		$(this.selectorHandler).hide();
	};

	fnOperator.edit = function(id){
		eve.dblclick.call(this.drawer.get(id));
	};

	fnOperator.forward = function(id){
		var one = this.drawer.getOneById(id);
		if(one)
			this.drawer.setToEnd(one);
	};

	fnOperator.plus = function(id){
		// add child
		var text = conf.initText.label;
		var newId = this.genId(id);
		this.drawer.create(newId, text, id);
	};

	// to be overwrite
	fnOperator.genId = function(id){
		var childrenSize = this.drawer.getChildren(id).length;
		return id + '_' + ++childrenSize;
	};

	fnOperator.showHandler = function(pos, e){
		$(this.selectorHandler)
			.css({left: pos.x, top: pos.y})
			.attr('data-id', pos.linkerId)
			.fadeIn('fast');
	};

	fnOperator.hideHandler = function(e){
		var target = $(e.toElement);
		if(!target.closest(this.selectorHandler).length)
			$(this.selectorHandler).fadeOut('fast');
	};

	fnOperator.showDetail = function(linker, e){
		this.render(linker);

		var opts = this.layouter.getDetailPosition(linker);
		$(this.selectorDetail)
			.hide()
			.css({left: opts.x, top: opts.y})
			.attr('data-id', linker.id)
			.fadeIn('fast');
	};

	fnOperator.hideDetail = function(linker, e){
		var target = $(e.toElement);
		// always true
		if(!target.closest(this.selectorDetail).length)
			$(this.selectorDetail).fadeOut('fast');
	};

	fnOperator.render = function(linker){
		$(this.selectorDetail).find('.pui-growl-title').text(linker.text);
		// change here if you wish
		$(this.selectorDetail).find('.pui-growl-content').text(linker.text + '...***...***...');
	};

	fnOperator.hideIfShow = function(id){
		var hideInner = function(one){
			if(!id || one.attr('data-id') === id)
				one.fadeOut('fast');
		};
		hideInner($(this.selectorHandler));
		hideInner($(this.selectorDetail));
	};

	// save all
	fnOperator.save = function(key){
		var list = this.drawer.serialize();
		this.storage.set(key, list);
	};

	// reload from storage
	fnOperator.fetch = function(key){
		var list = this.storage.get(key) || [];
		this.drawer.unserialize(list);
	};

})(this);